/* 
* sapdp-misc.c
*
* Copyright (c) 2010, Zachary N J Peterson <znpeters@nps.edu>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the Naval Postgraduate School nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ZACHARY N J PETERSON ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ZACHARY N J PETERSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "sapdp.h"

void printhex(unsigned char *ptr, size_t size){

	int i = 0;
	for(i = 0; i < size; i++){
		printf("%02X", *ptr);
		ptr++;
	}
	printf("\n");
}

void sfree(void *ptr, size_t size){ memset(ptr, 0, size); free(ptr); ptr = NULL;}


/* generate_fdh_h: the implementation of the full-domain psuedo-random fuction f_k().
 * This function first compute the pesudo-random function of input_prf with the secret PRF
 * key key->k_prf.  In this implementation we use HMAC-SHA1.
 * The implementation of the FDH function takes an input and its size and an RSA
 * modulus (in the form a SAPDP key).  It concatenates a counter to the input
 * and performs a SHA1 hash.  The result is appended to the final output.  This
 * process is repeated until the result is the size of the RSA modulus and the result
 * is less than the RSA modulus.  It returns a BIGNUM representation of the value or NULL
 * on failure.
 */
BIGNUM *generate_fdprf_f(SAPDP_key *key, unsigned char *input_prf, size_t input_prf_size){

	int n_bytes = 0;
	unsigned int num_hashes = 0;
	size_t sha1_input_size = input_prf_size + sizeof(unsigned int);
	unsigned char sha1_input[sha1_input_size];
	unsigned char *fdh = NULL;
	BIGNUM *fdh_bn = NULL;
	int i = 0;
	unsigned int counter = 0;
	unsigned char *prf_result = NULL;
	unsigned int prf_result_size = 0;

	if(!key || !input_prf || !input_prf_size) return NULL;

	if( ((prf_result = malloc(SHA_DIGEST_LENGTH)) == NULL)) goto cleanup;
	memset(prf_result, 0, SHA_DIGEST_LENGTH);
	
	/* Perform the HMAC on the block index */
	if(!HMAC(EVP_sha1(), key->k_prf, PRF_KEY_SIZE, input_prf, input_prf_size, 
		prf_result, (unsigned int *)&prf_result_size)) goto cleanup;	

	/* Get the size of the RSA modulus in bytes */
	n_bytes = BN_num_bytes(key->rsa->n);
	
	/* Calculate the number of hashes to perform minus one*/
	num_hashes = n_bytes/SHA_DIGEST_LENGTH;
	
	/* Allocate memory */
	if( ((fdh = malloc( (num_hashes + 1) * SHA_DIGEST_LENGTH )) == NULL)) goto cleanup;
	if( ((fdh_bn = BN_new()) == NULL)) goto cleanup;
	memset(fdh, 0, n_bytes);
	
	/* Fill all but the most significant bits of the fdh hash */
	counter = 0;
	for(i = num_hashes; i > 0; i--){
		/* Hash the output of the PRF appended with a counter */
		memset(sha1_input, 0, sha1_input_size);
		memcpy(sha1_input, &counter, sizeof(unsigned int));
		memcpy(sha1_input + sizeof(unsigned int), prf_result, prf_result_size);
		SHA1(sha1_input, sha1_input_size, (fdh + (i * SHA_DIGEST_LENGTH)));
		counter++;
	}
	/* Hash the most significant bits and re-hash until the FDH is smaller than the RSA modulus, N */
	do{
		memset(sha1_input, 0, sha1_input_size);
		memcpy(sha1_input, &counter, sizeof(unsigned int));
		memcpy(sha1_input + sizeof(unsigned int), prf_result, prf_result_size);
		if(!SHA1(sha1_input, sha1_input_size, fdh)) goto cleanup;
		/* Turn the first sizeof(rsa->n) bytes into a big number */
		if(!BN_bin2bn(fdh, n_bytes, fdh_bn)) goto cleanup;
		counter++;
	}while(BN_ucmp(fdh_bn, key->rsa->n) > 0);
	
	if(fdh) sfree(fdh, n_bytes);
	if(prf_result) sfree(prf_result, SHA_DIGEST_LENGTH);
	
	return fdh_bn;

cleanup:
	if(prf_result) sfree(prf_result, SHA_DIGEST_LENGTH);
	if(fdh) sfree(fdh, n_bytes);
	if(fdh_bn) BN_clear_free(fdh_bn);
	
	return NULL;
}

void destroy_sapdp_client_tag(SAPDP_client_tag *tag){

	if(!tag) return;
	if(tag->b) BN_clear_free(tag->b);
	if(tag->e) BN_clear_free(tag->e);
	if(tag->z) BN_clear_free(tag->z);
#ifdef USE_FDPRF
	if(tag->r) BN_clear_free(tag->r);
#else
	if(tag->r1) BN_clear_free(tag->r1);
	if(tag->r2) BN_clear_free(tag->r2);
#endif	
	sfree(tag, sizeof(SAPDP_client_tag));
	return;
}

SAPDP_client_tag *generate_sapdp_client_tag(){

	SAPDP_client_tag *tag = NULL;
	
	if( ((tag = malloc(sizeof(SAPDP_client_tag))) == NULL)) goto cleanup;
	memset(tag, 0, sizeof(SAPDP_client_tag));
  	if( ((tag->b = BN_new()) == NULL)) goto cleanup;
  	if( ((tag->e = BN_new()) == NULL)) goto cleanup;
  	if( ((tag->z = BN_new()) == NULL)) goto cleanup;
#ifdef USE_FDPRF
  	if( ((tag->r = BN_new()) == NULL)) goto cleanup;
#else
  	if( ((tag->r1 = BN_new()) == NULL)) goto cleanup;
  	if( ((tag->r2 = BN_new()) == NULL)) goto cleanup;
#endif	

	return tag;

cleanup:
	if(tag) destroy_sapdp_client_tag(tag);
	return NULL;
}

void destroy_sapdp_server_tag(SAPDP_server_tag *tag){

	if(!tag) return;
	if(tag->x) BN_clear_free(tag->x);
	if(tag->y) BN_clear_free(tag->y);
	sfree(tag, sizeof(SAPDP_server_tag));
	return;
}

SAPDP_server_tag *generate_sapdp_server_tag(){
	
	SAPDP_server_tag *tag = NULL;
	if( ((tag = malloc(sizeof(SAPDP_server_tag))) == NULL)) goto cleanup;
	memset(tag, 0, sizeof(SAPDP_server_tag));	
  	if( ((tag->x = BN_new()) == NULL)) goto cleanup;
  	if( ((tag->y = BN_new()) == NULL)) goto cleanup;

	return tag;
	
cleanup:
	if(tag) destroy_sapdp_server_tag(tag);
	return NULL;
}

void destroy_dpdp_tag(DPDP_tag *tag){

	if(!tag) return;
	if(tag->M) BN_clear_free(tag->M);
	sfree(tag, sizeof(SAPDP_server_tag));
	return;
}

DPDP_tag *generate_dpdp_tag(){
	
	DPDP_tag *tag = NULL;
	if( ((tag = malloc(sizeof(DPDP_tag))) == NULL)) goto cleanup;
	memset(tag, 0, sizeof(DPDP_tag));	
  	if( ((tag->M = BN_new()) == NULL)) goto cleanup;

	return tag;
	
cleanup:
	if(tag) destroy_dpdp_tag(tag);
	return NULL;
}
